/**
* MDocument - Document Implementation which binds to a value in a TextModel
*
* Copyright (c) 2002
* Marty Phelan, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.taursys.swing;
import javax.swing.text.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.taursys.model.*;
import com.taursys.model.event.*;
import com.taursys.util.*;
import java.util.*;
/**
* MDocument is a Document Implementation which binds to a value in a TextModel
* @author Marty Phelan
* @version 1.0
*/
public class MDocument extends PlainDocument implements ChangeListener {
private TextModel model;
// private com.taursys.model.ValueHolder valueHolder;
private boolean ignoreChangeEvent = false;
private boolean modified = false;
private boolean isRetrieving = false;
transient private Vector enableListeners;
/**
* Constructs a new MDocument and its default TextModel (not Document).
* The default model, a DefaultTextModel, is created via the
* createDefaultTextModel method. By default, the DefaultTextModel creates and
* uses a VariantValueHolder of type String.
*/
public MDocument() {
setModel(createDefaultTextModel());
retrieveValue();
}
/**
* Creates a new MDocument and a DefaultTextModel with a VariantValueHolder of the given type.
* See com.taursys.util.DataTypes for defined data type constants TYPE_XXXXXX.
* @throws UnsupportedDataTypeException if invalid javaDataType is given
*/
public MDocument(int javaDataType) throws UnsupportedDataTypeException {
setModel(new DefaultTextModel(javaDataType));
retrieveValue();
}
/**
* Returns the model for this component
*/
public TextModel getModel() {
return model;
}
/**
* Sets the TextModel used by this Document.
* If the given TextModel does not have a defined format, the format
* and pattern are copied from the current TextModel.
* Removes this Document as a change listener from the current
* TextModel (if any) and adds this as a change listener to the
* given TextModel.
*/
public void setModel(TextModel newModel) {
if (model != null) {
if (newModel.getFormat() == null) {
newModel.setFormat(model.getFormat());
newModel.setFormatPattern(model.getFormatPattern());
}
model.removeChangeListener(this);
}
model = newModel;
model.addChangeListener(this);
fireEnableChange(
new EnableEvent(this, isChangeable()));
}
/**
* Creates the default model used by this component
*/
protected TextModel createDefaultTextModel() {
return new DefaultTextModel();
}
/**
* Sets the Format of the TextModel.
*/
public void setFormat(java.text.Format format) {
model.setFormat(format);
}
/**
* Returns the Format of the TextModel.
*/
public java.text.Format getFormat() {
return model.getFormat();
}
/**
* Sets the Format patten of the TextModel.
*/
public void setFormatPattern(String newPattern) {
model.setFormatPattern(newPattern);
}
/**
* Returns the Format pattern of the TextModel.
*/
public String getFormatPattern() {
return model.getFormatPattern();
}
/**
* Sets the valueHolder for the model. The valueHolder is the object
* which holds the Object where the model stores the value. The
* default valueHolder is a VariantValueHolder with a javaDataType of String.
*/
public void setValueHolder(com.taursys.model.ValueHolder newValueHolder) {
model.setValueHolder(newValueHolder);
fireEnableChange(
new EnableEvent(this, isChangeable()));
}
/**
* Returns the valueHolder for the model. The valueHolder is the object
* which holds the Object where the model stores the value. The
* default valueHolder is a VariantValueHolder with a javaDataType of String.
*/
public com.taursys.model.ValueHolder getValueHolder() {
return model.getValueHolder();
}
/**
* Sets the propertyName in the valueHolder where the model stores the value.
* This name is ignored if you are using the default model (A DefaultTextModel
* with a VariantValueHolder).
*/
public void setPropertyName(String newPropertyName) {
model.setPropertyName(newPropertyName);
}
/**
* Returns the propertyName in the valueHolder where the model stores the value.
* This name is ignored if you are using the default model (A DefaultTextModel
* with a VariantValueHolder).
*/
public String getPropertyName() {
return model.getPropertyName();
}
/**
* Retrieves text from TextModel and stores in this Document
*/
public void retrieveValue() {
if (!isRetrieving) {
try {
isRetrieving = true;
remove(0, this.getLength());
insertString(0, model.getText(), null);
setModified(false);
} catch (Exception ex) {
/** @todo Invoke global exception handler */
ex.printStackTrace();
} finally {
isRetrieving = false;
}
}
}
/**
* Stores Document value in TextModel
* @throws ModelException if value cannot be stored because of invalid format or other reasons
*/
public void storeValue() throws ModelException {
try {
ignoreChangeEvent = true;
model.setText(getText(0, getLength()));
setModified(false);
} catch (BadLocationException ex) {
/** @todo Invoke global exception handler */
ex.printStackTrace();
} finally {
ignoreChangeEvent = false;
}
}
/**
* Invoked whenever a change event occurs so the value will be retrieved from the TextModel.
* Only responds if the event is a ContentChangeEvent and ignoreChangeEvent is
* not true. The value retrieved from the text model will be stored in the
* document.
*/
public void stateChanged(ChangeEvent e) {
if (!ignoreChangeEvent && e instanceof ContentChangeEvent) {
retrieveValue();
fireEnableChange(new EnableEvent(this,
!((ContentChangeEvent)e).isContentNull()));
}
}
/**
* Set the modified flag which indicates whether or not this document is modified.
* @param newModified the modified flag which indicates whether or not this document is modified.
*/
public void setModified(boolean newModified) {
modified = newModified;
}
/**
* Get the modified flag which indicates whether or not this document is modified.
* @return the modified flag which indicates whether or not this document is modified.
*/
public boolean isModified() {
return modified;
}
/**
* Updates document structure as a result of text insertion. This
* will happen within a write lock. Since this document simply
* maps out lines, we refresh the line map.
* Also marks this document as modified.
*
* @param chng the change event describing the dit
* @param attr the set of attributes for the inserted text
*/
protected void insertUpdate(AbstractDocument.DefaultDocumentEvent chng,
AttributeSet attr) {
super.insertUpdate( chng, attr);
}
/**
* Updates any document structure as a result of text removal.
* This will happen within a write lock. Since the structure
* represents a line map, this just checks to see if the
* removal spans lines. If it does, the two lines outside
* of the removal area are joined together.
* Also marks this document as modified.
*
* @param chng the change event describing the edit
*/
protected void removeUpdate(AbstractDocument.DefaultDocumentEvent chng) {
setModified(true);
super.removeUpdate(chng);
}
/**
* Creates a document leaf element.
* Hook through which elements are created to represent the
* document structure. Because this implementation keeps
* structure and content seperate, elements grow automatically
* when content is extended so splits of existing elements
* follow. The document itself gets to decide how to generate
* elements to give flexibility in the type of elements used.
* Also marks this document as modified.
*
* @param parent the parent element
* @param a the attributes for the element
* @param p0 the beginning of the range >= 0
* @param p1 the end of the range >= p0
* @return the new element
*/
protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
setModified(true);
return super.createLeafElement( parent, a, p0, p1);
}
/**
* Removes some content from the document.
* Removing content causes a write lock to be held while the
* actual changes are taking place. Observers are notified
* of the change on the thread that called this method.
* Also marks this document as modified.
* <p>
* This method is thread safe, although most Swing methods
* are not. Please see
* <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
* and Swing</A> for more information.
*
* @param offs the starting offset >= 0
* @param len the number of characters to remove >= 0
* @exception BadLocationException the given remove position is not a valid
* position within the document
* @see Document#remove
*/
public void remove(int offs, int len) throws BadLocationException {
setModified(true);
super.remove( offs, len);
}
/**
* Inserts some content into the document.
* Inserting content causes a write lock to be held while the
* actual changes are taking place, followed by notification
* to the observers on the thread that grabbed the write lock.
* Also marks this document as modified.
* <p>
* This method is thread safe, although most Swing methods
* are not. Please see
* <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
* and Swing</A> for more information.
*
* @param offs the starting offset >= 0
* @param str the string to insert; does nothing with null/empty strings
* @param a the attributes for the inserted content
* @exception BadLocationException the given insert position is not a valid
* position within the document
* @see Document#insertString
*/
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
setModified(true);
super.insertString( offs, str, a);
}
/**
* Creates a document branch element, that can contain other elements.
* Also marks this document as modified.
*
* @param parent the parent element
* @param a the attributes
* @return the element
*/
protected Element createBranchElement(Element parent, AttributeSet a) {
setModified(true);
return super.createBranchElement( parent, a);
}
/**
* Get indicator whether or not the the property value of the ValueHolder
* can be changed. A VO type ValueHolder can only be changed if its object
* is not null. A Collection type ValueHolder can only be changes if it is
* not empty.
*/
private boolean isChangeable() {
if (getValueHolder() == null)
return false;
if (getValueHolder() instanceof VOValueHolder) {
return ((VOValueHolder)getValueHolder()).getObject() != null;
} else if (getValueHolder() instanceof CollectionValueHolder) {
if (((CollectionValueHolder)getValueHolder()).isEmpty())
return false;
else if (getValueHolder() instanceof VOCollectionValueHolder ||
getValueHolder() instanceof VOListValueHolder)
return ((CollectionValueHolder)getValueHolder()).getObject() != null;
else
return true;
} else {
return true;
}
}
/**
* Removes the given listener from the list that is notified each time the
* enabled state changes. EnableEvents are generated whenever
* the contents of the ValueHolder change. They indicate whether or not a
* component can modify the current contents of the ValueHolder (not-null).
* @param l the EnableListener to remove from the notify list.
*/
public synchronized void removeEnableListener(EnableListener l) {
if (enableListeners != null && enableListeners.contains(l)) {
Vector v = (Vector) enableListeners.clone();
v.removeElement(l);
enableListeners = v;
}
}
/**
* Adds the given listener to the list that is notified each time the
* enabled state changes. EnableEvents are generated whenever the contents
* of the ValueHolder change. They indicate whether or not a component
* can modify the current contents of the ValueHolder (not-null).
* @param l the EnableListener to add to the notify list.
*/
public synchronized void addEnableListener(EnableListener l) {
Vector v = enableListeners == null ? new Vector(2) : (Vector) enableListeners.clone();
if (!v.contains(l)) {
v.addElement(l);
enableListeners = v;
}
}
/**
* Notify the listeners that the enabled state has changed. EnableEvents are
* generated whenever the contents of the ValueHolder change. They indicate
* whether or not a component can modify the current contents of the
* ValueHolder (not-null).
* @param e the EnableEvent to send to the listeners on the notify list.
*/
protected void fireEnableChange(EnableEvent e) {
if (enableListeners != null) {
Vector listeners = enableListeners;
int count = listeners.size();
for (int i = 0; i < count; i++) {
((EnableListener) listeners.elementAt(i)).enableChange(e);
}
}
}
}